/*
 Copyright (c) 2013- MeherTJ G.
All rights reserved. Everybody can use these code freely.
 */

#pragma once

#include <map>
#include <vector>
#include <string>
#include <functional>
#include "zen_ascii.h"

namespace Zen
{
    class CSVData
    {
      public:
        typedef std::vector<std::string> Row;
        typedef std::vector<Row> Rows;

        CSVData() = default;

        void Clear();

        bool Decode(std::string const& content);

        std::string Encode() const;

        Rows& GetRows();

        Rows const& GetRows() const;

      protected:
        Rows m_rows;
    };
} // namespace Zen

namespace Zen__
{
    using namespace Zen;

    struct CSVRowReader__
    {
      public:
        typedef CSVRowReader__ Me;
        std::string const& value;
        size_t& index;
        CSVData::Row& row;
        std::string temp;
        bool end;
        const char* _next_name = nullptr;

        std::function<bool()> _next;

        typedef bool (CSVRowReader__::*func)();

        inline bool _SET_NEXT(func f, const char* name, bool ret)
        {
            _next_name = name;
            if (f)
                _next = std::bind(f, this);
            else
                _next = nullptr;
            return ret;
        }
#define SET_NEXT_R(f, ret) _SET_NEXT(f, __func__, ret);
#define SET_NEXT(f) _SET_NEXT(f, __func__, true)
      public:
        CSVRowReader__(std::string const& data_, size_t& index_, CSVData::Row& row_)
          : value(data_)
          , index(index_)
          , row(row_)
        {
            end = false;
        }
        bool analyse()
        {
            SET_NEXT(&Me::readCol);
            while (_next != nullptr)
            {
                if (!_next()) return false;
            }
            return true;
        }

      private:
        bool readCol()
        {
            if (index < value.size() && value[index] == Ascii::DoubleQuote)
            {
                ++index;
                return SET_NEXT(&Me::readQuote);
            }
            else
            {
                return SET_NEXT(&Me::readNoQuote);
            }
        }
        bool readQuote()
        {
            if (index >= value.size())
                return SET_NEXT_R(nullptr, false)

                         auto c = value[index++];

            if (c == Ascii::DoubleQuote)
            {
                return SET_NEXT(&Me::onQuoteEnd);
            }
            else
            {
                temp.push_back(c);
                return SET_NEXT(&Me::readQuote);
            }
        }
        bool onQuoteEnd()
        {
            if (index >= value.size())
            {
                return SET_NEXT(&Me::onNoMore);
            }
            auto c = value[index++];

            if (c == Ascii::NewLine) return SET_NEXT(&Me::onEnd);
            if (c == Ascii::Return) return SET_NEXT(&Me::onRt);
            if (c == Ascii::Comma) return SET_NEXT(&Me::onSep);
            if (c == Ascii::DoubleQuote)
            {
                temp.push_back(Ascii::DoubleQuote);
                return SET_NEXT(&Me::readQuote);
            }
            else
            {
                return SET_NEXT_R(nullptr, false)
            }
        }
        bool readNoQuote()
        {
            if (index >= value.size()) return SET_NEXT(&Me::onNoMore);
            auto c = value[index++];
            if (c == Ascii::NewLine) return SET_NEXT(&Me::onEnd);
            if (c == Ascii::Return) return SET_NEXT(&Me::onRt);
            if (c == Ascii::Comma) return SET_NEXT(&Me::onSep);

            temp.push_back(c);
            return SET_NEXT(&Me::readNoQuote);
        }
        bool onNoMore()
        {
            row.push_back(temp);
            return SET_NEXT(nullptr);
        }
        bool onRt()
        {
            if (index >= value.size()) return SET_NEXT(&Me::onNoMore);
            if (value[index] == Ascii::NewLine)
            {
                ++index;
                return SET_NEXT(&Me::onEnd);
            }
            return SET_NEXT(&Me::onNoMore);
        }
        bool onEnd()
        {
            row.push_back(temp);
            return SET_NEXT(nullptr);
        }
        bool onSep()
        {
            row.push_back(temp);
            temp.clear();
            return SET_NEXT(&Me::readCol);
        }
    };
} // namespace Zen__
namespace Zen
{
    inline void CSVData::Clear()
    {
        m_rows.clear();
    }

    inline std::string CSVData::Encode() const
    {
        std::string res;
        for (auto& row : m_rows)
        {
            int cn = 0;
            for (auto& c : row)
            {
                if (cn) res.push_back(Zen__::Ascii::Comma);
                ++cn;

                if (c.find(Ascii::Comma) != c.npos || c.find(Ascii::DoubleQuote) != c.npos || c.find(Ascii::NewLine) != c.npos || c.find(Ascii::Return) != c.npos)
                {
                    res.push_back(Ascii::DoubleQuote);
                    for (auto i : c)
                    {
                        if (i == Ascii::DoubleQuote) res.push_back(Ascii::DoubleQuote);
                        res.push_back(i);
                    }
                    res.push_back(Ascii::DoubleQuote);
                }
                else
                {
                    res.append(c);
                }
            }
            res.push_back(Ascii::Return);
            res.push_back(Ascii::NewLine);
        }
        return res;
    }

    inline bool CSVData::Decode(std::string const& content)
    {
        size_t index = 0;
        while (index < content.size())
        {
            CSVData::Row row;
            Zen__::CSVRowReader__ fact(content, index, row);
            if (!fact.analyse())
            {
                return false;
            }
            m_rows.push_back(row);
        }
        return true;
    }

    inline CSVData::Rows& CSVData::GetRows()
    {
        return m_rows;
    }

    inline CSVData::Rows const& CSVData::GetRows() const
    {
        return m_rows;
    }
} // namespace Zen
